package com.tmsps.scan;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.tmsps.scan.config.Config;
import com.tmsps.scan.config.MenuScannerConstant;
import com.tmsps.scan.exception.MenuScannerException;
import com.tmsps.scan.util.FileUtil;

/**
 * @title MenuScannerUtil.java
 * @package com.nuoyun.scan
 * @description 扫描功能按钮
 * @author hanjiefei
 * @date 2019-08-05
 * @version V1.0
 * @Copyright 2019 nuoyun All rights reserved.
 */
public class MenuScannerUtil {

	private static List<Map<String, Object>> contentlist = new ArrayList<>();

	/**
	 * 获取当前路径下全部mcode及url
	 * @param path	扫描路径
	 * @return		List<Map<String, Object>>
	 */
	public static List<Map<String, Object>> getMcodeAndUrl(String path) {
		return getMcodeAndUrl(path, new Config());
	}
	
	/**
	 * 获取当前路径下全部url
	 * @param path	扫描路径
	 * @return		List<Map<String, Object>>
	 */
	public static List<Map<String, Object>> getAllUrls(String path) {
		return getAllUrls(path, new Config());
	}
	/**
	 * 获取当前路径下全部url
	 * @param path	扫描路径
	 * @return		List<Map<String, Object>>
	 */
	public static List<Map<String, Object>> getAllVueUrls(String path) {
		return getAllVueUrls(path, new Config(MenuScannerConstant.REQUIRE_SUFFIX_VUE, MenuScannerConstant.SUFFIX_VUE));
	}
	
	public static List<Map<String, Object>> getAllVueUrls(String path,Config config) {
		List<Map<String, Object>> mcodeAndUrl = new ArrayList<>();
		contentlist.clear();
		try {
			List<File> filelist = MenuScannerUtil.getFileList(path, config.getFileSuffix());
			String regex = config.getRequireSuffix();
			for (File file:filelist){
				MenuScannerUtil.formatContent(file,regex,path);
			}
			for(Map<String, Object> map:contentlist){
				mcodeAndUrl.addAll(getAllVueUrl(map,config));
			}
		} catch (IOException e) {
			throw new MenuScannerException(e);
		}
		return mcodeAndUrl;
	}
	
	/**
	 * 获取当前路径下全部mcode及url
	 * @param path		扫描路径
	 * @param config	配置
	 * @return		List<Map<String, Object>>
	 */
	public static List<Map<String, Object>> getMcodeAndUrl(String path,Config config) {
		List<Map<String, Object>> mcodeAndUrl = new ArrayList<>();
		contentlist.clear();
		try {
			List<File> filelist = MenuScannerUtil.getFileList(path, config.getFileSuffix());
			String regex = "<Auth:show";
			for (File file:filelist){
				MenuScannerUtil.formatContent(file,regex,path);
			}
			for(Map<String, Object> map:contentlist){
				map.put("id", UUID.randomUUID().toString().replaceAll("-", ""));
				mcodeAndUrl.addAll(getMcodeAndUrl(map,config));
			}
		} catch (IOException e) {
			throw new MenuScannerException(e);
		}
		return mcodeAndUrl;
	}
	
	/**
	 * 获取当前路径下全部url
	 * @param path		扫描路径
	 * @param config	配置
	 * @return		List<Map<String, Object>>
	 */
	public static List<Map<String, Object>> getAllUrls(String path,Config config) {
		List<Map<String, Object>> mcodeAndUrl = new ArrayList<>();
		contentlist.clear();
		try {
			List<File> filelist = MenuScannerUtil.getFileList(path, config.getFileSuffix());
			String regex = config.getRequireSuffix();
			for (File file:filelist){
				MenuScannerUtil.formatContent(file,regex,path);
			}
			for(Map<String, Object> map:contentlist){
				mcodeAndUrl.addAll(getAllUrl(map,config));
			}
		} catch (IOException e) {
			throw new MenuScannerException(e);
		}
		return mcodeAndUrl;
	}
	private static List<Map<String, Object>> getAllVueUrl(Map<String, Object> file,Config config) {
		String text = (String) file.get("content");
		List<Map<String, Object>> urlList = getAllVueUrl(text,config);
		List<Map<String, Object>> menuList = new ArrayList<>();
		for(int index=0;index<urlList.size();index++){
			Map<String, Object> map = new HashMap<>();
			String url = (String) urlList.get(index).get("text0");
			url=url.replace("Url: '", "");
			String name = file.get("name").toString();
			if ("index.vue".equals(name)) {
				String parent = file.get("parent").toString();
				name = parent.substring(parent.lastIndexOf(File.separator)+1);
			}else {
				name = name.substring(0,name.lastIndexOf('.'));
			}
			map.putAll(file);
			map.put("name", name);
			map.put("methodUrl", url.replaceAll("(')|(',)", ""));
			map.put("sort", (index+1));
			map.put("id", UUID.randomUUID().toString().replaceAll("-", ""));
			map.remove("content");
			menuList.add(map);
		}
		return menuList;
	}
	private static List<Map<String, Object>> getAllUrl(Map<String, Object> file,Config config) {
		String text = (String) file.get("content");
		List<Map<String, Object>> urlList = getAllUrl(text,config);
		List<Map<String, Object>> menuList = new ArrayList<>();
		for(int index=0;index<urlList.size();index++){
			Map<String, Object> map = new HashMap<>();
			String url = (String) urlList.get(index).get("text0");
			map.putAll(file);
			map.put("methodUrl", url.replace("{path}", ""));
			map.put("sort", (index+1));
			map.put("id", UUID.randomUUID().toString().replaceAll("-", ""));
			map.remove("content");
			menuList.add(map);
		}
		return menuList;
	}
	/**
	 * 获取按钮编号及按钮url
	 * @param config 
	 * @param text
	 * @return
	 */
	private static List<Map<String, Object>> getMcodeAndUrl(Map<String, Object> file, Config config) {
		String text = (String) file.get("content");
		List<Map<String, Object>> authList = getAuthButton(text);
		List<Map<String, Object>> menuList = new ArrayList<>();
		for(Map<String, Object> map:authList){
			String button = (String) map.get("text0");
			if (isNotNull(button)) {
				Map<String, Object> mcodeButton = getMcodeAndButton(button);
				mcodeButton.putAll(getMethod((String)mcodeButton.get("button")));
				mcodeButton.putAll(getMethodName((String)mcodeButton.get("button")));
				mcodeButton.putAll(getMethodUrl(text,(String)mcodeButton.get("methodClick"),config));
				mcodeButton.putAll(file);
				mcodeButton.remove("content");
				mcodeButton.remove("button");
//				Menu menu = fillMapToAnyBean(Menu.class, mcodeButton);
				menuList.add(mcodeButton);
			}
		}
		return menuList;
	}
	
	/**
	 * 获取文件中全部权限标签
	 * @param text
	 * @return
	 */
	private static List<Map<String, Object>> getAuthButton(String text) {
		String regex = "<Auth:show\\s+mcode=\"(.*?)\">(.*?)</Auth:show>";
		return getRegexTextList(regex, text);
	}
	/**
	 * 获取Vue文件中全部路径
	 * @param text
	 * @return
	 */
	private static List<Map<String, Object>> getAllVueUrl(String text,Config config) {
		String regex = String.format("Url:\\s+'(.*?)\\%s", config.getRequireSuffix());
		return getRegexTextList(regex, text);
	}
	/**
	 * 获取文件中全部请求
	 * @param text
	 * @return
	 */
	private static List<Map<String, Object>> getAllUrl(String text,Config config) {
		String regex = String.format("\\{path\\}\\/(.*?)\\%s", config.getRequireSuffix());
		return getRegexTextList(regex, text);
	}
	/**
	 * 获取文件中全部mcode和button
	 * @param text
	 * @return
	 */
	private static Map<String, Object> getMcodeAndButton(String text) {
		String regex = "<Auth:show\\s+mcode=\"(.*?)\">(.*?)</Auth:show>";
		return getRegexText(regex, text, "mcode","button");
	}
	/**
	 * 获取onclick方法
	 * @param text
	 * @return
	 */
	private static Map<String, Object> getMethod(String text) {
		String regex = "ms-on-click=\"(.*?)\"";
		Map<String, Object> map = getRegexText(regex, text, "methodClick");
		//avalon
		if (isNull(map.get("methodClick"))) {
			regex = "ms-click=\"(.*?)\"";
			map = getRegexText(regex, text, "methodClick");
		}
		//jQuery
		if (isNull(map.get("methodClick"))) {
			regex = "onclick=\"(.*?)\"";
			map = getRegexText(regex, text, "methodClick");
		}
		//layui
		if (isNull(map.get("methodClick"))) {
			regex = "lay-filter=\"(.*?)\"";
			map = getRegexText(regex, text, "methodClick");
		}
		return map;
	}
	/**
	 * 获取方法名称
	 * @param text
	 * @return
	 */
	private static Map<String, Object> getMethodName(String text) {
		String regex = "(<\\/i>(.*?)<\\/a>)";
		Map<String, Object> map = getRegexText(regex, text, "tmp","methodName");
		if (isNull(map.get("methodName"))) {
			regex = "(>(.*?)<\\/a>)";
			map = getRegexText(regex, text, "tmp","methodName");
		}
		if (isNull(map.get("methodName"))) {
			regex = "<\\/i>(.*?)<\\/button>";
			map = getRegexText(regex, text, "methodName");
		}
		if (isNull(map.get("methodName"))) {
			regex = ">(.*?)<\\/button>";
			map = getRegexText(regex, text, "methodName");
		}
		map.remove("tmp");
		return map;
	}
	/**
	 * 获取方法url
	 * @param text
	 * @param methodName
	 * @return
	 */
	private static Map<String, Object> getMethodUrl(String text,String methodName,Config config) {
		if (isNull(methodName)) {
			return new HashMap<>();
		}
		Map<String, Object> map = null;
		//layui
		if (methodName.indexOf("(")==-1) {
			String regex = String.format("form.on\\((.*?)\\(%s\\)(.*?)function\\((.*?)\\)\\{(.*?)\\{path\\}(.*?)\\%s", methodName, config.getRequireSuffix());
			map = getRegexText(regex, text, "tmp","method","tmp","tmp","methodUrl");
			map.remove("tmp");
		}else{
			//other
			String name = methodName.substring(0,methodName.indexOf("("));
			String regex = name+String.format("\\:function\\((.*?)\\)\\{(.*?)\\}\\%s", config.getRequireSuffix());
			regex = name+String.format("\\:function\\((.*?)\\)\\s?\\{(.*?)\\{path\\}(.*?)\\%s", config.getRequireSuffix());
			map = getRegexText(regex, text, "param","method","methodUrl");
			if (isNull(map.get("methodUrl"))) {
				regex =String.format("function\\s+%s\\((.*?)\\)\\s+\\{(.*?)\\{path\\}(.*?)\\%s",name, config.getRequireSuffix());
				map = getRegexText(regex, text, "param","method","methodUrl");
			}
			map.remove("param");
		}
		map.remove("method");
		String methodUrl = (String) map.get("methodUrl");
		map.put("methodUrl", methodUrl+config.getRequireSuffix());
		return map;
	}
	/**
	 * 根据正则表达式 提取文本内容
	 * @param regex
	 * @param text
	 * @param arg
	 * @return
	 */
	private static Map<String, Object> getRegexText(String regex,String text,String... arg) {
		Pattern pattern = Pattern.compile(regex);
		Matcher matcher = pattern.matcher(text);
		Map<String, Object> map = new HashMap<>();
		while(matcher.find()) {
			int count = matcher.groupCount();
			for(int index=0;index<count;index++){
//				System.out.println("getRegexText-->"+matcher.group(index));
				map.put(arg[index], matcher.group(index+1).trim());
			}
        }
		return map;
	}
	/**
	 * 根据正则表达式 提取全部符合要求的文本内容
	 * @param regex
	 * @param text
	 * @return
	 */
	private static List<Map<String, Object>> getRegexTextList(String regex,String text) {
		Pattern pattern = Pattern.compile(regex);
		Matcher matcher = pattern.matcher(text);
		List<Map<String, Object>> mapList = new ArrayList<>();
		while(matcher.find()) {
			int count = matcher.groupCount();
			for(int index=0;index<count;index++){
				Map<String, Object> map = new HashMap<>();
//				System.err.println("----->"+matcher.group(index));
				map.put("text"+index, matcher.group(index));
				mapList.add(map);
			}
        }
		return mapList;
	}
	
	/**
	 * 读取文件并格式化文件内容
	 * @param file	文件
	 * @param regex 表达式
	 * @param path	根路径
	 */
	private static void formatContent(File file,String regex,String path){
		String content = FileUtil.getFileText(file.getAbsolutePath());
		if (path.contains("WEB-INF")) {
			content = formatJSPContent(content);
		}else {
			content = formatVueContent(content);
		}
		if (content.indexOf(regex) != -1) {
			Map<String, Object> data = new HashMap<>();
			data.put("name", file.getName());
			data.put("absolutePath", file.getAbsolutePath());
			data.put("content", content);
			if (path.contains("WEB-INF")) {
				data.put("path", file.getPath().substring(path.lastIndexOf("WEB-INF")+7));
				data.put("parent", file.getParent().substring(path.lastIndexOf("WEB-INF")+7));
			}else {
				data.put("path", file.getPath());
				data.put("parent", file.getParent());
			}
			contentlist.add(data);
		}
	}
	/**
	 * 读取vue文件并格式化文件内容
	 * @param file	文件
	 * @param regex 表达式
	 * @param path	根路径
	 */
	private static String formatVueContent(String content) {
		content = content.replaceAll("(\r\n)|(\n)|(\t)+|(&nbsp;)+", " ");
		content = content.replaceAll("(\\s)+", " ");
		content = content.replaceAll("Url\\=\\s+", "Url: ");
		content = content.replaceAll("Url\\=", "Url: ");
		content = content.replaceAll("getAction\\(", "Url: ");
		return content;
	}
	/**
	 * 读取jsp文件并格式化文件内容
	 * @param file	文件
	 * @param regex 表达式
	 * @param path	根路径
	 */
	private static String formatJSPContent(String content) {
		content = content.replaceAll("(\r\n)|(\n)|(\t)+|(&nbsp;)+", " ");
		content = content.replaceAll("(\\s)+", " ");
		content = content.replaceAll("(\\{\\s+path\\})+", "\\{path\\}");
		content = content.replaceAll("(\\{\\s+path\\s+\\})+", "\\{path\\}");
		content = content.replaceAll("(\\{path\\s+\\})+", "\\{path\\}");
		content = content.replaceAll("(\\s+>)+", ">");
		content = content.replaceAll("src=\"(.*?)\"", "other_url");
		content = content.replaceAll("src='(.*?)'", "other_url");
		content = content.replaceAll("\\{path\\}(.*?)\\.jpg", "other_url");
		content = content.replaceAll("\\{path\\}(.*?)\\.png", "other_url");
		content = content.replaceAll("<link>(.*?)</link>", "other_url");
		content = content.replaceAll("<link(.*?)/>", "other_url");
		content = content.replaceAll("<link(.*?)>", "other_url");
		content = content.replaceAll("\\W+\\s?\\:\\s?function\\s?\\(", ":function(");
		content = content.replaceAll("<!--(.*?)-->", "html注释");
		content = content.replaceAll("<%--(.*?)--%>", "jsp注释");
		return content;
	}
	
	/**
	 * 扫描指定路径下全部文件
	 * @param dir		根路径
	 * @param suffix	文件后缀
	 * @return
	 * @throws IOException
	 */
	private static List<File> getFileList(String dir,String suffix) throws IOException {
		List<File> filelist = new ArrayList<>();
		Files.walkFileTree(Paths.get(dir), new FileVisitor<Path>() {
			@Override
			public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
				return FileVisitResult.CONTINUE;
			}
			@Override
			public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
				return FileVisitResult.CONTINUE;
			}
			@Override
			public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
				File data = file.toFile();
				if (data != null && data.getName().endsWith(suffix)) {
					filelist.add(data);
				}
				return FileVisitResult.CONTINUE;
			}
			@Override
			public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
				return FileVisitResult.CONTINUE;
			}
		});
		return filelist;
	}
	@SuppressWarnings("unused")
	private static <T> T fillMapToAnyBean(final Class<T> clazz, Map<String, String> map) {
		if (isNull(map)) {
			return null;
		}
		try {
			T obj = clazz.getDeclaredConstructor().newInstance();
			List<Field> fs = MenuScannerUtil.getClassFields(clazz);
			for (Field field : fs) {
				Object val = map.get(field.getName().replaceAll("_", "-"));
				MenuScannerUtil.setClassVal(field, obj, val);
			}
			return obj;
		} catch (Exception e) {
			throw new MenuScannerException(e);
		}
	}
	private static List<Field> getClassFields(Class<?> clazz) {
		List<Field> clazzField = new ArrayList<Field>();
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			clazzField.add(field);
		} // #for
		return clazzField;
	}
	private static void setClassVal(Field field, Object obj, Object val) {
		boolean acc = field.isAccessible();
		field.setAccessible(true);
		try {
			field.set(obj, val);
		} catch (Exception e) {
			throw new MenuScannerException(e);
		} finally {
			field.setAccessible(acc);
		}
	}
	private static boolean isNull(Object obj) {
		if (obj == null || obj == "") {
			return true;
		}
		return false;
	}
	private static boolean isNotNull(Object obj) {
		if (obj == null || obj == "") {
			return false;
		}
		return true;
	}
}
